﻿using System;
using System.Text;
using System.Threading;

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHI.OSHW.Hardware;

using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using GTI = Gadgeteer.Interfaces;

namespace Gadgeteer.Modules.BrainardTechnologies
{
    /// <summary>
    /// A nRF24L01Plus module for Microsoft .NET Gadgeteer
    /// This is a simple library to control the nRF24L01+ from Nordic
    /// https://www.nordicsemi.com/eng/Products/2.4GHz-RF/nRF24L01P
    ///
    /// This module an upgrade to v4.2 Gageteer of a module based on a v4.1 SPOT implimentation 
    /// The original was made by user: gralinPL on codeplex.com
    /// It can be found here: http://nrf24l01.codeplex.com/
    ///
    /// This Software is provided "as is" without warranty of any kind, either express or implied, 
    /// including without limitation any implied warranties of condition, uninterrupted use, merchantability, 
    /// fitness for a particular purpose, or non-infringement.
    /// </summary>
    /// 
    public class nRF24L01Plus : GTM.Module
    {
        // This is a simple library to control the nRF24L01+ from Nordic
        // https://www.nordicsemi.com/eng/Products/2.4GHz-RF/nRF24L01P
        //
        // This module an upgrade to v4.2 Gageteer of a module based on a v4.1 SPOT implimentation 
        // The original was made by user: gralinPL on codeplex.com
        // It can be found here: http://nrf24l01.codeplex.com/
        //
        // This Software is provided "as is" without warranty of any kind, either express or implied, 
        // including without limitation any implied warranties of condition, uninterrupted use, merchantability, 
        // fitness for a particular purpose, or non-infringement.

        #region Delegates

        public delegate void EventHandler();

        public delegate void OnDataRecievedHandler(byte[] data);

        public delegate void OnInterruptHandler(Status status);

        #endregion

        private GT.Socket _socket;
        private GT.Interfaces.DigitalOutput _cePin;
        private GT.Interfaces.DigitalOutput _csnPin;
        private bool _initialized;
        private GTI.InterruptInput _irqPin;
        private GTI.SPI _spi;
        private GTI.SPI.Configuration _spiConfig;

        /// <summary>
        ///   Gets a value indicating whether module is enabled (RX or TX mode).
        /// </summary>
        public bool IsEnabled
        {
            get { return _cePin.Read(); }
        }

        /// <summary>
        ///   Enables the module
        /// </summary>
        public void Enable()
        {
            _cePin.Write(true);
        }

        /// <summary>
        ///   Disables the module
        /// </summary>
        public void Disable()
        {
            _cePin.Write(false);
        }

        public void EnableInterrupts()
        {
            _irqPin.Interrupt += HandleInterrupt;
        }

        public void DisableInterrupts()
        {
            _irqPin.Interrupt += HandleInterrupt;
        }

        public nRF24L01Plus(int socketNumber)
        {
            // This finds the Socket instance from the user-specified socket number.  
            // This will generate user-friendly error messages if the socket is invalid.
            // If there is more than one socket on this module, then instead of "null" for the last parameter, 
            // put text that identifies the socket to the user (e.g. "S" if there is a socket type S)
            _socket = Socket.GetSocket(socketNumber, true, this, null);
            _socket.EnsureTypeIsSupported('S', this);

        }

        /// <summary>
        ///   Initializes SPI connection and control pins
        ///   <param name="irqPin"> IRQ pin as a Socket.Pin
        ///   <param name="cePin"> Chip Enable(CE) pin as a Socket.Pin
        ///   <param name="irqPin"> Chip Select Not(CSN or CS\) pin as a Socket.Pin
        ///   <param name="spiClockRateKHZ"> Clock rate in KHz (i.e. 1000 = 1MHz)
        /// </summary>
        public void Initialize(Socket.Pin irqPin, Socket.Pin cePin, Socket.Pin csnPin, uint spiClockRateKHZ = 1000)
        {
            _spiConfig = new GTI.SPI.Configuration(true, 0, 0, false, true, spiClockRateKHZ);

            // Chip Select : Active Low
            // Clock : Active High, Data clocked in on rising edge
            _spi = new GTI.SPI(_socket, _spiConfig, GTI.SPI.Sharing.Shared, this);

            //Initialize IRQ Port
            _irqPin = new GTI.InterruptInput(_socket, irqPin, GTI.GlitchFilterMode.Off, GTI.ResistorMode.PullUp,
                                       GTI.InterruptMode.FallingEdge, this);
            _irqPin.Interrupt += HandleInterrupt;

            // Initialize Chip Enable Port
            _cePin = new GT.Interfaces.DigitalOutput(_socket, cePin, false, this);

            //Initialize Chip Enable Port
            _csnPin = new GT.Interfaces.DigitalOutput(_socket, csnPin, false, this);

            // Module reset time
            Thread.Sleep(100);

            _initialized = true;
        }

        /// <summary>
        /// Configure the module basic settings. Module needs to be initiaized.
        /// </summary>
        /// <param name="pipe1address">RF address (3-5 bytes). The width of this address determins the width of all addresses used for sending/receiving.</param>
        /// <param name="channel">RF channel (0-127)</param>
        public void ConfigureWithTypicals(byte[] pipe1address, byte channel)
        {
            CheckIsInitialized();
            AddressWidth.Check(pipe1address);

            //Set Pipe 1 address
            SetAddress(AddressSlot.One, pipe1address);

            //CONFIG
            //Enable RX Data Ready Interrupt
            //Enable TX Data Send Interrupt
            //Enable Max Retries Interrupt
            //Put into Receive Mode
            //Power Up
            //CRC is 2 bytes (16 bits)
            //Enable CRC
            SetRegister(Registers.CONFIG, (byte)(
                    Enums.CONFIG_MASKS.RX_DR_IRQ_EN |
                    Enums.CONFIG_MASKS.TS_DS_IRQ_EN |
                    Enums.CONFIG_MASKS.MAX_RT_IRQ_EN |
                    Enums.CONFIG_MASKS.PRX |
                    Enums.CONFIG_MASKS.PWR_UP |
                    Enums.CONFIG_MASKS.CRC_2BYTE |
                    Enums.CONFIG_MASKS.CRC_EN));

            //EN_AA
            //Enable AutoAcknowledge in all pipes
            SetRegister(Registers.EN_AA, (byte)(
                    Enums.EN_AA.ENAA_ALL));

            //EN_RXADDR
            //Enable Pipe 0
            //Enable Pipe 1            
            SetRegister(Registers.EN_RXADDR, (byte)(
                        Enums.EN_RXADDR.ERX_P0 |
                        Enums.EN_RXADDR.ERX_P1));
            //SETUP_RETR
            //Retransmit 15 times
            //AutoRetransmit Delay is 1000us
            SetRegister(Registers.SETUP_RETR, (byte)(
                    Enums.SETUP_RETR.ARC_RETRANS_15 |
                    Enums.SETUP_RETR.ARD_WAIT_1000ms));

            //SETUP_AW
            //Address Width is 5 bytes
            SetRegister(Registers.SETUP_AW, (byte)(
                Enums.SETUP_AW.AW_5bytes));

            //RF_CH
            //RF Channel is 76 (0x4C)
            SetRegister(Registers.RF_CH, (byte)(76));

            //RF_SETUP
            //Data Rate is 250kbps for best range
            //Set to 0bBm (Max power)
            SetRegister(Registers.RF_SETUP, (byte)(
                Enums.RF_SETUP.RF_DR_250kbps |
                Enums.RF_SETUP.RF_PWR_0dBm));

            //FEATURE
            //Enable Dynamic Payloads
            SetRegister(Registers.FEATURE, (byte)(
                Enums.FEATURE.EN_DPL));

            //DYNPD
            //Allow Dynamic Payloads on all Pipes
            SetRegister(Registers.DYNPD, (byte)(
                Enums.DYNPD.DPL_ALL));

            //RX_PW_P0
            //Set register length to 32 bytes (max)
            SetRegister(Registers.RX_PW_P0, (byte)(0x20));

            //RX_PW_P1
            //Set register length to 32 bytes (max)
            SetRegister(Registers.RX_PW_P1, (byte)(0x20));

            //Clear both RX and TX buffers
            Execute(Commands.FLUSH_RX, 0x00, new byte[0]);
            Execute(Commands.FLUSH_TX, 0x00, new byte[0]);
        }

        /// <summary>
        /// Set one of 6 available module addresses
        /// </summary>
        public void SetAddress(AddressSlot slot, byte[] address)
        {
            CheckIsInitialized();
            AddressWidth.Check(address);
            Execute(Commands.W_REGISTER, (byte)slot, address);
        }

        /// <summary>
        /// Read 1 of 6 available module addresses
        /// </summary>
        public byte[] GetAddress(AddressSlot slot, int width)
        {
            CheckIsInitialized();
            AddressWidth.Check(width);
            var read = Execute(Commands.R_REGISTER, (byte)slot, new byte[width]);
            var result = new byte[read.Length - 1];
            Array.Copy(read, 1, result, 0, result.Length);
            return result;
        }

        /// <summary>
        ///   Get an entire Register
        /// </summary>
        /// <param name = "register">Register to read</param>
        /// <returns>Response byte. Register value</returns>
        public byte GetRegister(byte register)
        {
            CheckIsInitialized();
            var read = Execute(Commands.R_REGISTER, register, new byte[1]);
            var result = new byte[read.Length - 1];
            Array.Copy(read, 1, result, 0, result.Length);
            return read[1];
        }

        /// <summary>
        ///   Write an entire Register
        /// </summary>
        /// <param name = "register">Register to write to</param>
        /// <param name = "value">Value to be set</param>
        /// <returns>Response byte. Register value after write</returns>
        public byte SetRegister(byte register, byte value)
        {
            CheckIsInitialized();
            Execute(Commands.W_REGISTER, register, new byte[] { value });
            return GetRegister(register);
        }

        /// <summary>
        ///   Sets/Resets a bit in the module
        /// </summary>
        /// <param name = "register">Register to write to</param>
        /// <param name = "mnemonic">Bit position. Use value from Bits.xxxx</param>
        /// <param name = "value">Value to be set(true or false)</param>
        /// <returns>Response byte. Register value after write</returns>
        public byte SetConfigBit(byte register, byte mnemonic, bool value)
        {
            int RegisterValue = GetRegister(register);

            RegisterValue = RegisterValue & ~(1 << mnemonic);

            if (value)
            {
                RegisterValue |= (1 << mnemonic);
            }

            Execute(Commands.W_REGISTER, register,
                        new[]
                        {
                            (byte)RegisterValue
                        });

            return GetRegister(register);
        }

        /// <summary>
        ///   Executes a command in NRF24L01+ (for details see module datasheet)
        /// </summary>
        /// <param name = "command">Command</param>
        /// <param name = "address">Register to write to</param>
        /// <param name = "data">Data to write</param>
        /// <returns>Response byte array. First byte is the status register</returns>
        public byte[] Execute(byte command, byte address, byte[] data)
        {
            CheckIsInitialized();

            _csnPin.Write(false);

            var wasEnabled = IsEnabled;

            // This command requires module to be in power down or standby mode
            if (command == Commands.W_REGISTER)
            {
                Disable();
            }

            // Create SPI Buffers with Size of Data + 1 (For Command)
            var writeBuffer = new byte[data.Length + 1];
            var readBuffer = new byte[data.Length + 1];

            // Add command and address to SPI buffer
            writeBuffer[0] = (byte)(command | address);

            // Add data to SPI buffer
            Array.Copy(data, 0, writeBuffer, 1, data.Length);

            // Do SPI Read/Write
            _spi.WriteRead(writeBuffer, readBuffer);

            // Enable module back if it was disabled
            if (command == Commands.W_REGISTER && wasEnabled)
            {
                Enable();
            }

            _csnPin.Write(true);

 
            // Return ReadBuffer
            return readBuffer;
        }

        /// <summary>
        ///   Gets module basic status information
        /// </summary>
        public Status GetStatus()
        {
            CheckIsInitialized();
            _csnPin.Write(false);
            var readBuffer = new byte[1];
            _spi.WriteRead(new[] { Commands.NOP }, readBuffer);
            _csnPin.Write(true);
            return new Status(readBuffer[0]);
        }

        /// <summary>
        ///   Send <param name = "bytes">bytes</param> to given <param name = "address">address</param>
        /// </summary>
        public void SendTo(byte[] address, byte[] bytes)
        {
            // Pull down the CE in order to prepare for the send pulse
            Disable();

            //Put the module in transmit mode
            EnableTXMode();

            //Make sure we're set up to auto ack
            SetAddress(AddressSlot.Zero, address);

            // Write transmit adres to TX_ADDR register. 
            Execute(Commands.W_REGISTER, Registers.TX_ADDR, address);
            
            // Send payload
            Execute(Commands.W_TX_PAYLOAD, 0x00, bytes);

            // Pulse for CE -> starts the transmission.
            Enable();

            //Give some time to send
            Thread.Sleep(5);
        }

        private void HandleInterrupt(GTI.InterruptInput sender, bool value)
        {
            if (!_initialized)
            {
                return;
            }

            // Disable RX/TX
            Disable();

            // Set PRX
            SetConfigBit(Registers.CONFIG, (int)Enums.CONFIG.PRIM_RX, true);

            var status = GetStatus();
            var payloads = new byte[3][];
            byte payloadCount = 0;
            var payloadCorrupted = false;

            OnInterrupt(status);

            if (status.DataReady)
            {
                while (!status.RxEmpty)
                {
                    // Read payload size
                    var payloadLength = Execute(Commands.R_RX_PL_WID, 0x00, new byte[1]);

                    // this indicates corrupted data
                    if (payloadLength[1] > 32)
                    {
                        payloadCorrupted = true;

                        // Flush anything that remains in buffer
                        Execute(Commands.FLUSH_RX, 0x00, new byte[0]);
                    }
                    else
                    {
                        // Read payload data
                        payloads[payloadCount] = Execute(Commands.R_RX_PAYLOAD, 0x00, new byte[payloadLength[1]]);
                        payloadCount++;
                    }

                    // Clear RX_DR bit 
                    var result = SetConfigBit(Registers.STATUS, Bits.RX_DR, true);
                    status.Update(result);
                }
            }

            if (status.ResendLimitReached)
            {
                // Flush TX FIFO 
                Execute(Commands.FLUSH_TX, 0x00, new byte[0]);

                // Clear MAX_RT bit in status register
                SetConfigBit(Registers.STATUS, Bits.MAX_RT, true);
                OnResendLimitReached();
            }

            if (status.TxFull)
            {
                // Flush TX FIFO 
                Execute(Commands.FLUSH_TX, 0x00, new byte[0]);
                OnTxFull();
            }

            if (status.DataSent)
            {
                // Clear TX_DS bit in status register
                SetConfigBit(Registers.STATUS, Bits.TX_DS, true);
                OnDataSendSuccess();
            }

            // Enable RX
            Enable();

            if (payloadCorrupted)
            {
                Debug.Print("Corrupted data received");
            }
            else if (payloadCount > 0)
            {
                for (var i = 0; i < payloadCount; i++)
                {
                    var payload = payloads[i];
                    var payloadWithoutCommand = new byte[payload.Length - 1];
                    Array.Copy(payload, 1, payloadWithoutCommand, 0, payload.Length - 1);
                    OnDataReceived(payloadWithoutCommand);
                }
            }
        }

        /// <summary>
        ///   Put the module into RX Mode
        /// </summary>
        public void EnableRXMode()
        {
            SetConfigBit(Registers.CONFIG, Bits.PRIM_RX, true);

            // wait for the radio just in case
            Thread.Sleep(1);
        }

        /// <summary>
        ///   Put the module into TX Mode
        /// </summary>
        public void EnableTXMode()
        {
            // Transmitter power-up
            SetConfigBit(Registers.CONFIG, Bits.PRIM_RX, false);

            // wait for the radio just in case
            Thread.Sleep(1);
        }

        private void CheckIsInitialized()
        {
            if (!_initialized)
            {
                throw new InvalidOperationException("Initialize method needs to be called before this call");
            }
        }

        /// <summary>
        ///   Called on every IRQ interrupt
        /// </summary>
        public event OnInterruptHandler OnInterrupt = delegate { };

        /// <summary>
        ///   Occurs when data packet has been received
        /// </summary>
        public event OnDataRecievedHandler OnDataReceived = delegate { };

        /// <summary>
        ///   Occurs when max retries have occured
        /// </summary>
        public event EventHandler OnResendLimitReached = delegate { };

        /// <summary>
        ///   Occurs when TX Buffer is full
        /// </summary>
        public event EventHandler OnTxFull = delegate { };

        /// <summary>
        ///   Occurs when TX Sent Successfully
        /// </summary>
        public event EventHandler OnDataSendSuccess = delegate { };

    }
}
